package org.sbbs.base.pagequery.hibernate;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.beanutils.converters.BooleanConverter;
import org.apache.commons.beanutils.converters.DateConverter;
import org.apache.commons.beanutils.converters.DoubleConverter;
import org.apache.commons.beanutils.converters.FloatConverter;
import org.apache.commons.beanutils.converters.IntegerConverter;
import org.apache.commons.beanutils.converters.LongConverter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Criteria;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Junction;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Restrictions;
import org.hibernate.impl.CriteriaImpl;
import org.hibernate.impl.CriteriaImpl.Subcriteria;
import org.sbbs.base.searcher.SearchFilter;
import org.sbbs.base.searcher.SearchRule;

/*
 * TODO  此工具类需要仔细的完善
 * 1、String 到 Object的转换使用ConvertUtilsBean工具类进行统一转换
 * 2、针对不同数据类型总结出适用的操作符号组，创建搜索条件统一处理
 */
public class CriteriaUtil {
	// http://wenku.baidu.com/view/d0dbaca4284ac850ad024224.html
	protected static final Log log = LogFactory.getLog(CriteriaUtil.class);

	public static Criteria getHibernateCriteria(Criteria criteria,
			final Class object, SearchFilter searchFilter) {
		if (searchFilter.isMultiSearch() == false) {
			SearchRule rule = searchFilter.getRules().get(0);
			if (log.isDebugEnabled())
				log.debug("single search option" + rule.toString());

			try {
				criteria = CriteriaUtil.createAliasCriteria(
						(CriteriaImpl) criteria, rule.getField());
				criteria.add(CriteriaUtil.createCriterion(object, rule
						.getField(), rule.getOp(), rule.getData()));
			} catch (Exception e) {
				e.printStackTrace();
			}
		} else {
			Junction op = buildMultiCriterion(criteria, object, searchFilter);
			Iterator itrg = searchFilter.getGroups().iterator();
			while (itrg.hasNext()) {
				SearchFilter sf = (SearchFilter) itrg.next();
				Junction sfop = buildMultiCriterion(criteria, object,
						searchFilter);
				op.add(sfop);
			}
			criteria.add(op);
		}
		if (log.isDebugEnabled())
			log.debug("criteria build over,criteria is:" + criteria.toString());
		return criteria;
	}

	private static Junction buildMultiCriterion(Criteria criteria,
			final Class object, SearchFilter searchFilter) {
		Junction op = null;
		if (searchFilter.getGroupOp().equalsIgnoreCase("and"))
			op = Restrictions.conjunction();
		else if (searchFilter.getGroupOp().equalsIgnoreCase("or"))
			op = Restrictions.disjunction();
		Iterator itrr = searchFilter.getRules().iterator();
		while (itrr.hasNext()) {
			SearchRule sr = (SearchRule) itrr.next();
			try {
				criteria = CriteriaUtil.createAliasCriteria(
						(CriteriaImpl) criteria, sr.getField());
				op.add(CriteriaUtil.createCriterion(object, sr.getField(), sr
						.getOp(), sr.getData()));
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		return op;
	}

	/*
	 * TODO 多级别名的问题没有处理
	 */
	public static Criteria createAliasCriteria(Criteria prim, String field) {
		return createAliasCriteria(prim, field, -1);
	}

	public static Criteria createAliasCriteria(Criteria prim, String field,
			int joinType) {

		String path = null;
		try {
			path = field.substring(0, field.lastIndexOf("."));
		} catch (Exception e) {
			path = null;
		}

		if (path == null)
			return prim;
		else {

			String[] fields = field.split("\\.");
			String[] alPathes = getAliasPath(field);
			for (int i = 0; i < alPathes.length; i++) {
				boolean aliasExist = false;
				Iterator itr = ((CriteriaImpl) prim).iterateSubcriteria();
				while (itr.hasNext()) {
					Subcriteria ci = (Subcriteria) itr.next();
					if (ci.getAlias().equals(alPathes[i])) {
						aliasExist = true;
						break;
					}
				}
				if (!aliasExist) {
					if (joinType == CriteriaSpecification.LEFT_JOIN
							|| joinType == CriteriaSpecification.FULL_JOIN
							|| joinType == CriteriaSpecification.INNER_JOIN) {
						prim.createAlias(alPathes[i], fields[i], joinType);
					} else {
						prim.createAlias(alPathes[i], fields[i]);
					}
				}
			}
		}
		return prim;
	}

	private static String[] getAliasPath(String field) {
		String[] sf = field.split("\\.");
		List list = new ArrayList();
		String path = null;
		for (int i = 0; i < sf.length - 1; i++) {
			for (int j = 0; j <= i; j++) {
				if (j == 0)
					path = sf[j];
				else
					path = path + "." + sf[j];
			}
			list.add(path);
		}

		String[] al = new String[list.size()];

		list.toArray(al);
		return al;
	}

	private static String getSearchAliasFieldname(String field) {
		String[] sf = field.split("\\.");
		if (sf.length <= 1)
			return field;
		else
			return sf[sf.length - 2] + "." + sf[sf.length - 1];
	}

	/**
	 * [
	 * 'eq','ne','lt','le','gt','ge','bw','bn','in','ni','ew','en','cn','nc'
	 * ]
	 * 
	 * ['equal','not equal', 'less', 'less or equal','greater','greater or
	 * equal', 'begins with','does not begin with','is in','is not in',
	 * 'ends with','does not end with','contains','does not contain']
	 * 
	 * @throws Exception
	 */
	public static Criterion createCriterion(Class clazz, String field,
			String oper, String data) throws Exception {
		try {

			Class fieldType = null;
			fieldType = getFieldTypeName(clazz, field);
			String fieldTypeName = fieldType.getName();
			if (fieldTypeName.equals("java.lang.String")) {
				field = getSearchAliasFieldname(field);
				return createStringCriterion(field, oper, data);
			}
			if (fieldTypeName.equals("java.util.Date")
					|| fieldTypeName.equalsIgnoreCase("java.sql.Date")) {
				Object obj = new DateConverter().convert(fieldType, data);
				return createDateCriterion(field, oper, obj);
			}
			if (fieldTypeName.equals("java.lang.Boolean")
					|| fieldTypeName.equalsIgnoreCase("boolean")) {
				Object obj = new BooleanConverter().convert(fieldType, data);
				field = getSearchAliasFieldname(field);
				return createBooleanCriterion(field, oper, obj);
			}
			if (fieldTypeName.equals("java.lang.Integer")
					|| fieldTypeName.equalsIgnoreCase("int")) {
				Object obj = new IntegerConverter().convert(fieldType, data);
				field = getSearchAliasFieldname(field);
				return createNumberCriterion(field, oper, obj);
			}
			if (fieldTypeName.equals("java.lang.Double")
					|| fieldTypeName.equalsIgnoreCase("double")) {
				Object obj = new DoubleConverter().convert(fieldType, data);
				field = getSearchAliasFieldname(field);
				return createNumberCriterion(field, oper, obj);
			}
			if (fieldTypeName.equals("java.lang.Float")
					|| fieldTypeName.equalsIgnoreCase("float")) {
				Object obj = new FloatConverter().convert(fieldType, data);
				field = getSearchAliasFieldname(field);
				return createNumberCriterion(field, oper, obj);
			}
			if (fieldTypeName.equals("java.lang.Long")
					|| fieldTypeName.equalsIgnoreCase("long")) {
				Object obj = new LongConverter().convert(fieldType, data);
				field = getSearchAliasFieldname(field);
				return createNumberCriterion(field, oper, obj);
			}

		} catch (Exception e) {
			throw e;
		}

		return null;
	}

	private static Class getFieldTypeName(Class clazz, String field)
			throws SecurityException, NoSuchFieldException {
		Class fieldType = null;
		String fieldTypeName;
		String[] sf = field.split("\\.");
		for (int i = 0; i < sf.length; i++) {
			fieldType = clazz.getDeclaredField(sf[i]).getType();
			clazz = fieldType;
			// fieldTypeName = fieldType.getName();
		}
		fieldTypeName = fieldType.getName();
		return fieldType;
	}

	/**
	 * ['eq', 'ne', 'lt', 'le', 'gt', 'ge', 'bw', 'bn', 'in', 'ni', 'ew',
	 * 'en', 'cn', 'nc']
	 * 
	 */
	private static Criterion createStringCriterion(String field, String oper,
			Object data) {
		if (oper.equalsIgnoreCase("eq")) {
			return Restrictions.eq(field, data).ignoreCase();
		} else if (oper.equalsIgnoreCase("ne")) {
			return Restrictions.ne(field, data).ignoreCase();
		} else if (oper.equalsIgnoreCase("bw")) {
			// data = data + "%";
			return Restrictions.like(field, (String) data, MatchMode.START)
					.ignoreCase();
		} else if (oper.equalsIgnoreCase("ew")) {
			// data = "%" + data;
			return Restrictions.like(field, (String) data, MatchMode.END)
					.ignoreCase();
		} else if (oper.equalsIgnoreCase("cn")) {
			// data = "%" + data + "%";
			// criteria.add(Restrictions.like(pf.getPropertyName(),
			// (String)pf.getMatchValue(), MatchMode.ANYWHERE));
		    String v = (String) data;
            boolean bp = v.startsWith( "*" );
            boolean ep = v.endsWith( "*" );
            v = v.replaceAll( "\\*", "" );
            if ( bp && ep )
                return Restrictions.like(field, v, MatchMode.ANYWHERE).ignoreCase();
            else if ( bp && !ep )
                return Restrictions.like(field, v, MatchMode.END).ignoreCase();
            else if ( !bp && ep )
                return Restrictions.like(field, v, MatchMode.START).ignoreCase();
            else
                return Restrictions.like(field, v, MatchMode.ANYWHERE).ignoreCase();
		    
		    
		}

		return null;
	}

	/**
	 * ['eq', 'ne', 'lt', 'le', 'gt', 'ge', 'bw', 'bn', 'in', 'ni', 'ew',
	 * 'en','cn', 'nc']
	 */
	private static Criterion createDateCriterion(String field, String oper,
			Object data) {
		if (oper.equalsIgnoreCase("eq")) {
			return Restrictions.eq(field, data);
		} else if (oper.equalsIgnoreCase("ne")) {
			return Restrictions.ne(field, data);
		} else if (oper.equalsIgnoreCase("lt")) {
			return Restrictions.lt(field, data);
		} else if (oper.equalsIgnoreCase("le")) {
			return Restrictions.le(field, data);
		} else if (oper.equalsIgnoreCase("gt")) {
			return Restrictions.gt(field, data);
		} else if (oper.equalsIgnoreCase("ge")) {
			return Restrictions.ge(field, data);
		}

		return null;
	}

	/**
	 * ['eq', 'ne', 'lt', 'le', 'gt', 'ge', 'bw', 'bn', 'in', 'ni', 'ew',
	 * 'en','cn', 'nc']
	 * 
	 */
	private static Criterion createBooleanCriterion(String field, String oper,
			Object data) {
		if (oper.equalsIgnoreCase("eq")) {
			return Restrictions.eq(field, data);
		} else if (oper.equalsIgnoreCase("ne")) {
			return Restrictions.ne(field, data);
		}
		return null;
	}

	private static Criterion createNumberCriterion(String field, String oper,
			Object data) {
		if (oper.equalsIgnoreCase("eq")) {
			return Restrictions.eq(field, data);
		} else if (oper.equalsIgnoreCase("ne")) {
			return Restrictions.ne(field, data);
		} else if (oper.equalsIgnoreCase("lt")) {
			return Restrictions.lt(field, data);
		} else if (oper.equalsIgnoreCase("le")) {
			return Restrictions.le(field, data);
		} else if (oper.equalsIgnoreCase("gt")) {
			return Restrictions.gt(field, data);
		} else if (oper.equalsIgnoreCase("ge")) {
			return Restrictions.ge(field, data);
		}

		return null;
	}
}
